<?php

/**
 * Simple form component that checks whether its value is the same as a given passord field.
 */
class ConfirmPasswordField extends PasswordField {
	
	/**
	 * @param UnchallengedPasswordComponent $password_field PasswordComponent or UnchallengedPasswordComponent object to check against.
	 */
	public function __construct(UnchallengedPasswordComponent $password_field) {
		parent::__construct();
		$this->add_check(new ConfirmPasswordCheck(
			$this->get_js_getter(), 
			$password_field->get_js_unhashed_getter()
		));
		// Normally not needed, but just to make sure the plain password is not send to the server
		$this->add_js_postprocessing("{$this->get_js_setter()}('');");
	}
	
}


class ConfirmPasswordCheck extends ClientSideCheck {
	
	private $getter_1;
	private $getter_2;
	
	public function __construct($getter_1, $getter_2) {
		$this->getter_1 = $getter_1;
		$this->getter_2 = $getter_2;
	}
	
	public function comment($alias) {
		return 'The 2 passwords are not equal.';
	}
	
	public function validate_js($value) { return "
		return {$this->getter_1}() == {$this->getter_2}();
	";}
	
}


/**
 * A password field that does some cool security stuff automatically.
 * 
 * Passwords are hashed before they are sent, with a salt. 
 */
class UnchallengedPasswordComponent extends DoubleTagFormInput {
	
	private $hash_preprocessing_added = false; 
	
	public function __construct($claim_password) {
		parent::__construct(
			new PasswordField(),
			new HiddenField($claim_password)
		);
		$this->add_check(new MinLengthCheck(6));
	}
	
	public function get_js_preprocessing() {
		if (!$this->hash_preprocessing_added) {
			$this->add_hash_preprocessing();
			$this->hash_preprocessing_added = true;
		}
		return parent::get_js_preprocessing();
	}
	
	protected function add_hash_preprocessing() {
		$unhashed_getter = $this->get_visible()->get_js_getter();
		$hashed_setter = $this->get_main()->get_js_setter();
		$SYSTEM_SALT = SYSTEM_SALT;
		$this->add_js_preprocessing("$hashed_setter(Sha1.hash($unhashed_getter() + '$SYSTEM_SALT'));");
	}
	
	// For password confirm access
	public function get_js_unhashed_getter() {
		return $this->get_visible()->get_js_getter();
	}
	
}


/**
 * 
 * An extra hash step is used with the challenge added. If the challenge is
 * different every time, this password sent over the internet will also 
 * be different every time, so that reusing a (malciously obtained) 
 * hashed password is not possible.
 * 
 * The challenge itself is not send back to the server. Otherwise a 
 * hacker could choose the challenge, and he will choose the one that 
 * was used to hash the password when he was eavesdropped your 
 * password! 
 *  
 * (In order for the server to know what challenge was used,
 * you could incorporate a challenge ID field in your form.)
 */
class PasswordComponent extends UnchallengedPasswordComponent {
	
	private $challenge;
	
	public function __construct($claim_password, $challenge) {
		parent::__construct($claim_password);
		$this->challenge = $challenge;
	}
	
	// Slightly more complex hash preprocessing
	public function add_hash_preprocessing() {
		$unhashed_getter = $this->get_visible()->get_js_getter();
		$hashed_setter = $this->get_main()->get_js_setter();
		$SYSTEM_SALT = SYSTEM_SALT;
		$this->add_js_preprocessing("$hashed_setter(Sha1.hash(Sha1.hash($unhashed_getter() + '$SYSTEM_SALT') + '{$this->challenge}'));");
	}
	
}


